class Draw {
  constructor(canvas, context) {
    this.canvas = canvas;
    this.ctx = context;
  }

  roundRect(x, y, w, h, r, fill = true, stroke = false) {
    if (r < 0) return;
    const ctx = this.ctx;
    ctx.beginPath();
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
    ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0);
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);
    ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);
    ctx.lineTo(x, y + r);
    if (stroke) ctx.stroke();
    if (fill) ctx.fill();
  }

  drawView(box, style) {
    const ctx = this.ctx;
    const {
      left: x,
      top: y,
      width: w,
      height: h
    } = box;
    const {
      borderRadius = 0,
      borderWidth = 0,
      borderColor,
      color = '#000',
      backgroundColor = 'transparent'
    } = style;
    ctx.save(); // 外环

    if (borderWidth > 0) {
      ctx.fillStyle = borderColor || color;
      this.roundRect(x, y, w, h, borderRadius);
    } // 内环


    ctx.fillStyle = backgroundColor;
    const innerWidth = w - 2 * borderWidth;
    const innerHeight = h - 2 * borderWidth;
    const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0;
    this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius);
    ctx.restore();
  }

  async drawImage(img, box, style) {
    await new Promise((resolve, reject) => {
      const ctx = this.ctx;
      const canvas = this.canvas;
      const {
        borderRadius = 0
      } = style;
      const {
        left: x,
        top: y,
        width: w,
        height: h
      } = box;
      ctx.save();
      this.roundRect(x, y, w, h, borderRadius, false, false);
      ctx.clip();
      const Image = canvas.createImage();

      Image.onload = () => {
        ctx.drawImage(Image, x, y, w, h);
        ctx.restore();
        resolve();
      };

      Image.onerror = () => {
        reject();
      };

      Image.src = img;
    });
  } // eslint-disable-next-line complexity


  drawText(text, box, style) {
    const ctx = this.ctx;
    let {
      left: x,
      top: y,
      width: w,
      height: h
    } = box;
    let {
      color = '#000',
      lineHeight = '1.4em',
      fontSize = 14,
      textAlign = 'left',
      verticalAlign = 'top',
      backgroundColor = 'transparent'
    } = style;
    if (!text || lineHeight > h) return;
    ctx.save();

    if (lineHeight) {
      // 2em
      lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize);
    }

    ctx.textBaseline = 'top';
    ctx.font = `${fontSize}px sans-serif`;
    ctx.textAlign = textAlign; // 背景色

    ctx.fillStyle = backgroundColor;
    this.roundRect(x, y, w, h, 0); // 文字颜色

    ctx.fillStyle = color; // 水平布局

    switch (textAlign) {
      case 'left':
        break;

      case 'center':
        x += 0.5 * w;
        break;

      case 'right':
        x += w;
        break;

      default:
        break;
    }

    const textWidth = ctx.measureText(text).width;
    const actualHeight = Math.ceil(textWidth / w) * lineHeight;
    let paddingTop = Math.ceil((h - actualHeight) / 2);
    if (paddingTop < 0) paddingTop = 0; // 垂直布局

    switch (verticalAlign) {
      case 'top':
        break;

      case 'middle':
        y += paddingTop;
        break;

      case 'bottom':
        y += 2 * paddingTop;
        break;

      default:
        break;
    }

    const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2); // 不超过一行

    if (textWidth <= w) {
      ctx.fillText(text, x, y + inlinePaddingTop);
      return;
    } // 多行文本


    const chars = text.split('');
    const _y = y; // 逐行绘制

    let line = '';

    for (const ch of chars) {
      const testLine = line + ch;
      const testWidth = ctx.measureText(testLine).width;

      if (testWidth > w) {
        ctx.fillText(line, x, y + inlinePaddingTop);
        y += lineHeight;
        line = ch;
        if (y + lineHeight > _y + h) break;
      } else {
        line = testLine;
      }
    } // 避免溢出


    if (y + lineHeight <= _y + h) {
      ctx.fillText(line, x, y + inlinePaddingTop);
    }

    ctx.restore();
  }

  async drawNode(element) {
    const {
      layoutBox,
      computedStyle,
      name
    } = element;
    const {
      src,
      text
    } = element.attributes;

    if (name === 'view') {
      this.drawView(layoutBox, computedStyle);
    } else if (name === 'image') {
      await this.drawImage(src, layoutBox, computedStyle);
    } else if (name === 'text') {
      this.drawText(text, layoutBox, computedStyle);
    }

    const childs = Object.values(element.children);

    for (const child of childs) {
      await this.drawNode(child);
    }
  }

}

module.exports = {
  Draw
};